# coding=utf-8

import datetime
import math
import os
import shutil
import time
import warnings

import PyPDF2
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from PyPDF2 import PdfFileReader, PdfFileWriter
from matplotlib.backends.backend_pdf import PdfPages
from scipy.signal import savgol_filter

compound_input = ['2,2_dimethylsuccinic acid',
                  '2_Hydroxycaproic acid',
                  '3_Hydroxyphenylacetic acid',
                  '4_Methylhexanoic acid',
                  'Alpha_ketoisovaleric acid',
                  'Alpha_Lactose',
                  'beta_Chenodeoxycholic acid',
                  'Beta_Ursodeoxycholic Acid',
                  'Butyrylcarnitine',
                  'Gamma_Linolenic acid',
                  'Glycodeoxycholic acid',
                  'Glycylproline',
                  'Isobutyric acid',
                  'Isobutyrylcarnitine',
                  'Isocaproic acid',
                  'L_Alanine',
                  'N_Acetyl_L_aspartic acid',
                  'p_Hydroxyphenylacetic acid',
                  'Picolinic acid',
                  'Undecanoic acid',
                  'Ursodeoxycholic acid',
                  'L_Lysine',
                  'L_Histidine',
                  'L_Arginine',
                  'Ornithine',
                  'Aminocaproic acid',
                  'Erythronic acid',
                  'Phenyllactic acid',
                  'Ethylmalonic Acid',
                  '4_Hydroxyproline',
                  'N_alpha_Acetyl_L_lysine',
                  'Hydroxypropionic acid',
                  '5_Dodecenoic acid',
                  'Ornithine']


def simplify_compound_name(compound_name):
    compound_name = compound_name.replace('-', ' ')
    compound_name = compound_name.replace('_', ' ')
    return compound_name.lower()

matplotlib.use('agg')
# 禁用警告信息
warnings.filterwarnings("ignore")
STD_CONC_DICT = {'Conc A': [0.0, 0.05, 0.1, 0.5, 1.0, 2.0, 4.0, 8.0, 16.0],
                 'Conc B': [0.0, 0.0125, 0.025, 0.125, 0.25, 0.5, 1.0, 2.0, 4.0],
                 'Conc C': [0.0, 0.125, 0.25, 1.25, 2.5, 5.0, 10.0, 20.0, 40.0],
                 'Conc D': [0.0, 1.25, 2.5, 12.5, 25.0, 50.0, 100.0, 200.0, 400.0],
                 'Conc E': [0.0, 12.5, 25.0, 125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0],
                 'Conc F': [0.0, 6.25, 12.5, 62.5, 125.0, 250.0, 500.0, 1000.0, 2000.0],
                 'Conc G': [0.0, 1.56, 3.12, 15.6, 31.25, 62.5, 125.0, 250.0, 500.0],
                 'Conc H': [0.0, 0.625, 1.25, 6.25, 12.5, 25.0, 50.0, 100.0, 200.0],
                 'Conc I': [0.0, 0.31, 0.625, 3.12, 6.25, 12.5, 25.0, 50.0, 100.0],
                 'Conc J': [0.0, 3.1, 6.25, 31.2, 62.5, 125.0, 250.0, 500.0, 1000.0],
                 'Conc K': [0.0, 0.0625, 0.125, 0.625, 1.25, 2.5, 5.0, 10.0, 20.0]}

pd.set_option('display.max_rows', None)
pd.set_option('display.max_columns', None)
pd.set_option('display.colheader_justify', 'center')
pd.set_option('display.width', 1000)


def force_zero_k(conc_lst, respond_lst):
    assert len(conc_lst) == len(respond_lst)
    sum1, sum2 = 0, 0
    for idx, i in enumerate(conc_lst):
        xy = conc_lst[idx] * respond_lst[idx]
        xx = conc_lst[idx] * conc_lst[idx]
        sum1 += xy
        sum2 += xx
    return sum1 / sum2


def polyfot(x, y, k, b):
    y_pred = [k * i + b for i in x]
    y_pred = np.array(y_pred)
    y_average = np.mean(y)
    y_real = np.array(y)
    SSR = np.sum((y_pred - y_average) ** 2)
    SSE = np.sum((y_pred - y_real) ** 2)
    SST = SSR + SSE
    if SST == 0:
        return 1
    else:
        return SSR / SST


def calculate_area(intensity_list, time_list, time_left, time_right):
    if len(time_list) < 2:
        return 0
    if len(intensity_list) < 2:
        return 0
    assert len(time_list) == len(intensity_list)
    x = int(len(intensity_list) * 0.2)
    noise = np.mean(sorted(intensity_list)[:x])
    if time_left is not None and time_right is not None:
        a = list(zip(time_list, intensity_list))
        b = [i for i in a if time_left <= i[0] <= time_right]
        area = 0
        if len(b) > 1:
            for i in range(len(b) - 1):
                left_time, left_intensity, right_time, right_intensity = b[
                                                                             i][0], b[i][1], b[i + 1][0], b[i + 1][1]
                area = area + (right_time - left_time) * (left_intensity + right_intensity) * 0.5 - (
                        right_time - left_time) * noise

        if area > 0:
            return area
        else:
            return 0
    else:
        return 0


def insert_bookmark(pdf, bookmark_dict):
    reader = PdfFileReader(pdf)
    writer = PdfFileWriter()
    writer.cloneDocumentFromReader(reader)
    bookmarks = []

    blue_lst, red_lst, green_lst, pink_lst = [], [], [], []
    all_lst = []
    for compound in bookmark_dict:
        color = bookmark_dict[compound][2]
        if color == (0, 0, 255):
            blue_lst.append(compound)
        elif color == (255, 0, 0):
            red_lst.append(compound)
        elif color == (0, 255, 0):
            green_lst.append(compound)
        elif color == (51, 0, 102):
            pink_lst.append(compound)
        all_lst.append(compound)

    all_bookmark = writer.addBookmark(title='全部', pagenum=0, parent=None, color=(0, 0, 0), bold=True)
    blue_bookmark = writer.addBookmark(title='蓝色', pagenum=0, parent=None, color=(0, 0, 255), bold=True)
    red_bookmark = writer.addBookmark(title='红色', pagenum=0, parent=None, color=(255, 0, 0), bold=True)
    green_bookmark = writer.addBookmark(title='绿色', pagenum=0, parent=None, color=(0, 255, 0), bold=True)
    pink_bookmark = writer.addBookmark(title='粉色', pagenum=0, parent=None, color=(51, 0, 102), bold=True)

    for compound in bookmark_dict:
        # new_bookmark = writer.addBookmark(title=bookmark_dict[compound][0], pagenum=bookmark_dict[compound][1],
        #                                   parent=None,
        #                                   color=bookmark_dict[compound][2], bold=True)
        if compound in red_lst:
            new_bookmark = writer.addBookmark(title=bookmark_dict[compound][0], pagenum=bookmark_dict[compound][1],
                                              parent=red_bookmark,
                                              color=bookmark_dict[compound][2], bold=True)
        elif compound in blue_lst:
            new_bookmark = writer.addBookmark(title=bookmark_dict[compound][0], pagenum=bookmark_dict[compound][1],
                                              parent=blue_bookmark,
                                              color=bookmark_dict[compound][2], bold=True)
        elif compound in green_lst:
            new_bookmark = writer.addBookmark(title=bookmark_dict[compound][0], pagenum=bookmark_dict[compound][1],
                                              parent=green_bookmark,
                                              color=bookmark_dict[compound][2], bold=True)
        elif compound in pink_lst:
            new_bookmark = writer.addBookmark(title=bookmark_dict[compound][0], pagenum=bookmark_dict[compound][1],
                                              parent=pink_bookmark,
                                              color=bookmark_dict[compound][2], bold=True)

        bookmarks.append(new_bookmark)

    for compound in bookmark_dict:
        new_bookmark = writer.addBookmark(title=bookmark_dict[compound][0], pagenum=bookmark_dict[compound][1],
                                          parent=all_bookmark,
                                          color=bookmark_dict[compound][2], bold=True)
        bookmarks.append(new_bookmark)
    with open(pdf, 'wb') as f:
        writer.write(f)


def produce_page_dict(com_color_dict=None, pdf_dir=None, compound_lst=None, compound_index_dict=None):
    bookmark_dict = {}
    for idx, compound in enumerate(compound_lst):
        index_name = compound_index_dict[compound].split("_")[0]
        pdf_path = os.path.join(pdf_dir, compound + ".pdf")
        reader = PdfFileReader(pdf_path)
        page = reader.getNumPages()
        idx = compound_lst.index(compound)
        name = index_name + "_" + compound

        color = com_color_dict[compound]

        book_info = (name, page * idx, color)
        bookmark_dict[compound] = book_info
    return bookmark_dict


def split_num(num, n):
    distance = int(num / n)
    lst = []
    for idx in range(n):
        lst.append(idx * distance)
    lst.append(num)
    return lst


def read_color_info(color_file, color_group):
    if color_file is None:
        return None
    if color_file and color_group:
        if '.xlsx' in color_file:
            color_df = pd.read_excel(color_file)
        else:
            color_df = pd.read_csv(color_file)
        color_dict = {}
        compound_lst = list(color_df['compound'])
        color_lst = list(color_df["color_" + color_group])
        conc_lst = list(color_df["conc_" + color_group])
        for idx, com in enumerate(compound_lst):
            color_dict[com] = {}
            color_dict[com]['color'] = color_lst[idx]
            color_dict[com]['conc'] = conc_lst[idx]
        return color_dict
    else:
        return None


def narrow_range(intensity_list, time_list, left, right):
    assert len(intensity_list) == len(time_list)
    narrowed_intensity_list, narrowed_time_list = [], []
    for idx, i in enumerate(time_list):
        if left <= i <= right:
            narrowed_time_list.append(i)
            narrowed_intensity_list.append(intensity_list[idx])
    return np.array(narrowed_intensity_list), np.array(narrowed_time_list)


def calculate_noise(intensity_list):
    x = int(len(intensity_list) * 0.2)
    noise = np.mean(sorted(intensity_list)[:x])
    return noise


class GetIntensity(object):
    def __init__(self, ms_file=None):
        self.ms_file = ms_file
        with open(self.ms_file, 'r', encoding='UTF-8') as f:  # 打开新的文本
            self.content = f.readlines()

    def get_binary(self, content):
        assert isinstance(content, list)
        lst_lst = []
        for line in content:
            line = line.lstrip().rstrip()

            if 'binary: [' in line:
                j = line.split(']')[1]
                j = j.split()
                j = [float(x) for x in j if x]
                lst_lst.append(j)

            if 'Q1' in line:
                part = line.split()
                for _ in part:
                    if 'Q1' in _:
                        mass1 = float(_.split('=')[1])
                    if 'Q3' in _:
                        mass2 = float(_.split('=')[1])
        return (mass1, mass2), lst_lst

    def get_profile_dict(self):
        profiles_dict = {}
        # 对行数进行遍历
        line_num_lst = []
        for idx, i in enumerate(self.content):
            i = i.lstrip().rstrip()
            if i:
                if 'Q1=' in i:
                    line_num_lst.append(idx)
        line_num_lst.append(len(self.content))
        for i in zip(line_num_lst, line_num_lst[1:]):
            begin_line_num = i[0]
            end_line_num = i[1]
            paragraph = self.content[begin_line_num:end_line_num]
            (mass1, mass2), lst_lst = self.get_binary(paragraph)
            if len(lst_lst) == 2:
                time_lst, intensity_lst = lst_lst
                profiles_dict[(mass1, mass2)] = {}
                profiles_dict[(mass1, mass2)]['time'] = time_lst
                profiles_dict[(mass1, mass2)]['intensity'] = intensity_lst
        return profiles_dict


class GetInfoDict(object):
    def __init__(self, compound_list=None, peak_list=None,
                 file_list=None, conc_list=None, intensity_list=None):
        self.peak_data = pd.read_excel(peak_list, index_col='sample')
        self.conc_data = pd.read_excel(conc_list)
        self.conc_dict = self.get_conc_dict()
        self.file_list = file_list
        self.intensity_list = intensity_list

        self.batch_std_dict = self.get_batch_std_dict()
        self.intensity_dict = self.get_intensity_dict()
        df = pd.read_excel(compound_list)
        com = list(df['Compound'])
        self.sample_lst = list(self.peak_data.index)
        self.compound_lst = com
        rt = list(df['RT'])
        std = list(df['STD'])
        com_index = list(df['Index'])
        IS = list(df['IS_correction'])
        origin = list(df['Origin'])
        mass1, mass2 = [float(i.split(">")[0].strip()) for i in df['Mass']], [float(i.split(">")[1].strip()) for i in
                                                                              df['Mass']]
        m_m = list(zip(mass1, mass2))

        self.com_origin_dict = dict(zip(com, origin))
        self.com_mm_dict = dict(zip(com, m_m))
        self.com_rt_dict = dict(zip(com, rt))
        self.com_index_dict = dict(zip(com, com_index))
        self.index_com_dict = dict(zip(com_index, com))
        self.index_com_dict = dict(zip(com_index, com))
        self.com_IS_correction_dict = dict(zip(com, IS))

        self.batch_sample_dict = self.get_batch_sample_dict()
        self.range_dict = self.get_range_dict()

        self.com_IS_dict = {}
        for i in com:
            IS_index = self.com_IS_correction_dict[i]
            if IS_index in com_index:
                com_IS = self.index_com_dict[IS_index]
            else:
                com_IS = i
            self.com_IS_dict[i] = com_IS
        self.com_std_dict = dict(zip(com, std))

        self.peak_dict = {}
        for compound in com:
            self.peak_dict[compound] = {}
            for txt in self.sample_lst:
                txt_name = txt.split('.')[0]
                self.peak_dict[compound][txt_name] = {}

    def get_com_range_rsd_dict(self):
        com_range_rsd_dict = {}
        for idx, compound in enumerate(self.compound_lst):
            dis_lst = []
            for idx_2, sample in enumerate(self.sample_lst):
                if 'std_' not in sample:
                    left = self.range_dict[compound][sample]['left']
                    if left:
                        right = self.range_dict[compound][sample]['right']
                        dis = abs(left - right)
                        dis_lst.append(dis)
            if len(dis_lst) >= 3 and np.average(dis_lst) > 0.2:
                rsd = np.std(dis_lst, ddof=1) / np.average(dis_lst)
                com_range_rsd_dict[compound] = rsd
        lst1, lst2 = [], []
        for i in com_range_rsd_dict:
            lst1.append(self.com_index_dict[i])
            lst2.append(com_range_rsd_dict[i])
        df = pd.DataFrame({'compound': lst1, 'rsd': lst2})
        return com_range_rsd_dict

    def get_range_dict(self):
        range_dict = {}
        peak_compound_lst = list(self.peak_data.columns)
        for compound in self.compound_lst:
            range_dict[compound] = {}
            for idx_2, sample in enumerate(self.sample_lst):
                range_dict[compound][sample] = {}
                idx = peak_compound_lst.index(compound)
                cell_value = str(self.peak_data.iloc[idx_2][idx])
                value_lst = cell_value.split(',')
                if len(value_lst) == 3:
                    left, rt, right = float(
                        value_lst[0]), float(
                        value_lst[1]), float(
                        value_lst[2])
                    range_dict[compound][sample]['left'] = left
                    range_dict[compound][sample]['rt'] = rt
                    range_dict[compound][sample]['right'] = right
                else:
                    range_dict[compound][sample]['left'] = None
                    range_dict[compound][sample]['rt'] = None
                    range_dict[compound][sample]['right'] = None
        return range_dict

    def get_peak_dict(self):
        for txt in self.file_list:
            txt_name = os.path.basename(txt).split('.')[0]
            if txt_name in self.sample_lst:
                profiles_dict = GetIntensity(ms_file=txt).get_profile_dict()
                for compound in self.compound_lst:
                    mass_pair = self.com_mm_dict[compound]
                    profile = profiles_dict[mass_pair]
                    self.peak_dict[compound][txt_name] = profile
        return self.peak_dict

    def get_rt_dict(self):
        return self.com_rt_dict

    def get_index_dict(self):
        return self.com_index_dict

    def get_conc_dict(self):
        rsd_row_idx = list(self.conc_data['sample']).index('RSD(%)')
        lst1 = list(self.conc_data['sample'][rsd_row_idx + 3:])
        lst2 = list(self.conc_data.columns[2:])
        df = self.conc_data.iloc[rsd_row_idx + 3:, 2:]
        df.index = lst1

        com_txt_conc_dict = {}
        for idx, com in enumerate(lst2):
            com_txt_conc_dict[com] = {}
            for idx1, txt in enumerate(lst1):
                com_txt_conc_dict[com][txt] = df.iloc[idx1][idx]
        return com_txt_conc_dict

    def get_std8_dict(self):
        rsd_row_idx = list(self.conc_data['sample']).index('RSD(%)')
        sample_lst = list(self.conc_data['sample'][rsd_row_idx + 3:])
        batch_lst = list(self.conc_data['batch'][rsd_row_idx + 3:])
        # sample_batch_dict = dict(zip(sample_lst, batch_lst))
        batch_std8_dict = {}

        std8_lst = [i for i in sample_lst if "_std8" in i.lower()
                    or "_std_8" in i.lower()]
        for idx, i in enumerate(sample_lst):
            if i in std8_lst:
                batch = batch_lst[idx]
                batch_std8_dict[batch] = i

        sample_std8_dict = {}
        for idx, sample in enumerate(sample_lst):
            batch = batch_lst[idx]
            std8 = batch_std8_dict[batch]
            sample_std8_dict[sample] = std8

        return sample_std8_dict

    def get_batch_std_dict(self):
        rsd_row_idx = list(self.conc_data['sample']).index('RSD(%)')
        sample_lst = list(self.conc_data['sample'][rsd_row_idx + 3:])
        batch_lst = list(self.conc_data['batch'][rsd_row_idx + 3:])

        batch_std_dict = {}
        batch_set = set(batch_lst)
        for batch in batch_set:
            batch_std_dict[batch] = []
            for idx, bat in enumerate(batch_lst):
                sample = sample_lst[idx]
                if bat == batch:
                    if '_std' in sample.lower():
                        batch_std_dict[batch].append(sample_lst[idx])
        return batch_std_dict

    def get_batch_sample_dict(self):
        rsd_row_idx = list(self.conc_data['sample']).index('RSD(%)')
        sample_lst = list(self.conc_data['sample'][rsd_row_idx + 3:])
        batch_lst = list(self.conc_data['batch'][rsd_row_idx + 3:])
        batch_sample_dict = {}
        batch_set = set(batch_lst)
        for batch in batch_set:
            batch_sample_dict[batch] = []
            for idx, bat in enumerate(batch_lst):
                sample = sample_lst[idx]
                if bat == batch:
                    if '_std' not in sample.lower():
                        batch_sample_dict[batch].append(sample_lst[idx])
        return batch_sample_dict

    def get_intensity_dict(self):
        if '.csv' in self.intensity_list:
            self.intensity_df = pd.read_csv(self.intensity_list)
        else:
            self.intensity_df = pd.read_excel(self.intensity_list)
        sample_list = list(self.intensity_df['sample'])
        compound_list = self.intensity_df.columns[2:]
        intensity_dict = {}
        for compound in compound_list:
            intensity_dict[compound] = {}
            for idx, sample in enumerate(sample_list):
                intensity_dict[compound][sample] = list(
                    self.intensity_df[compound])[idx]
        return intensity_dict

    def get_compound_batch_std_respond_dict(self):
        respond_dict = {}
        batch_set = set(self.batch_std_dict)
        for compound in self.compound_lst:
            for batch in batch_set:
                compound_batch_std_respond_lst = self.get_compound_batch_std_respond_lst(
                    compound, batch)
                respond_dict[(compound, batch)
                ] = compound_batch_std_respond_lst
        return respond_dict

    def get_compound_batch_std_respond_lst(self, compound, batch):
        std_lst = self.batch_std_dict[batch]
        std_IS = self.com_IS_dict[compound]
        lst = []
        for std_sample in std_lst:
            std_intensity = self.intensity_dict[compound][std_sample]
            std_IS_intensity = self.intensity_dict[std_IS][std_sample]
            if compound == std_IS:
                respond = std_intensity
            else:
                if std_IS_intensity != 0:
                    respond = std_intensity / std_IS_intensity
                else:
                    respond = 0
            lst.append(respond)
        return lst

    def get_compound_batch_std_area_lst(self, compound, batch):
        std_lst = self.batch_std_dict[batch]
        lst = []
        for std_sample in std_lst:
            std_intensity = self.intensity_dict[compound][std_sample]
            lst.append(std_intensity)
        return lst

    def detect_std_coef(self, k_cutoff):
        compound_lst = self.compound_lst
        batch_set = set(self.batch_std_dict)
        compound_k_dict = {}
        for compound in compound_lst:
            compound_k_dict[compound] = {}

        k_compound_lst, coef_lst = [], []
        batch_lst, batch_04_lst = [], []
        k_04_compound_lst, coef_04_lst = [], []
        com_lst, com_04_lst = [], []
        origin_lst, origin_04_lst = [], []
        for compound in compound_lst:
            compound_k_dict[compound] = {}
            std = self.com_std_dict[compound]
            if std in STD_CONC_DICT:
                std_conc_lst = STD_CONC_DICT[std]
                std_04_conc_lst = std_conc_lst[:5]
                for batch in batch_set:
                    std_responds_lst = self.get_compound_batch_std_respond_lst(
                        compound, batch)
                    std_area_lst = self.get_compound_batch_std_area_lst(
                        compound, batch)
                    std_04_area_lst = std_area_lst[:5]

                    if self.com_origin_dict[compound] == 'Exclude':
                        k, b = np.polyfit(
                            np.array(std_conc_lst), np.array(std_responds_lst), 1)
                    else:
                        k, b = force_zero_k(std_conc_lst, std_responds_lst), 0

                    coef = polyfot(std_conc_lst, std_responds_lst, k, b)

                    if coef < k_cutoff:
                        com = self.com_index_dict[compound]
                        if com not in k_compound_lst:
                            k_compound_lst.append(com)
                            coef_lst.append(coef)
                            batch_lst.append(batch)
                            com_lst.append(compound)
                            origin_lst.append(self.com_origin_dict[compound])

                    if self.com_origin_dict[compound] == 'Exclude':
                        k_04, b_04 = np.polyfit(
                            np.array(std_04_conc_lst), np.array(std_04_area_lst), 1)
                    else:
                        k_04, b_04 = force_zero_k(
                            std_04_conc_lst, std_04_area_lst), 0
                    coef_04 = polyfot(
                        std_04_conc_lst, std_04_area_lst, k_04, b_04)

                    sample_lst = self.batch_sample_dict[batch]
                    length = 0
                    for sample in sample_lst:
                        sample_intensity = self.intensity_dict[compound][sample]
                        if sample_intensity <= max(std_04_area_lst):
                            length = length + 1
                    if length / len(sample_lst) >= 0.8 and coef_04 < 0.7:
                        com = self.com_index_dict[compound]
                        if com not in k_04_compound_lst:
                            k_04_compound_lst.append(com)
                            coef_04_lst.append(coef_04)
                            batch_04_lst.append(batch)
                            com_04_lst.append(compound)
                            origin_04_lst.append(
                                self.com_origin_dict[compound])

        print("std0-std8 R cutoff: ", k_cutoff)
        df = pd.DataFrame({'index': k_compound_lst, 'compound': com_lst, 'R': coef_lst, 'batch': batch_lst,
                           'origin': origin_lst}, index=None)
        print(df)
        print("——" * 50)

        print("std0-std4 R cutoff: %s,  sample percent: %s: " % (0.75, "70%"))
        df = pd.DataFrame({'index': k_04_compound_lst, 'compound': com_04_lst, 'R': coef_04_lst, 'batch': batch_04_lst,
                           'origin': origin_04_lst}, index=None)
        print(df)

        for i in com_04_lst:
            if i not in com_lst:
                com_lst.append(i)
        return com_lst

    def detect_std8_area(self):
        std8_low_area_com_lst = []
        std8_low_area_lst = []
        batch_lst = []
        com_lst = []
        for compound in self.compound_lst:
            for batch in self.batch_std_dict:
                std_lst = self.batch_std_dict[batch]
                for std in std_lst:
                    if "_std8" in std.lower() or "_std_8" in std.lower():
                        std8_area = self.intensity_dict[compound][std]
                        if std8_area < 5000:
                            com = self.com_index_dict[compound]
                            std8_low_area_com_lst.append(com)
                            std8_low_area_lst.append(int(std8_area))
                            batch_lst.append(batch)
                            com_lst.append(compound)

        print("std8 area cutoff: ", 5000)
        df = pd.DataFrame(
            {'index': std8_low_area_com_lst, 'compound': com_lst,
             'area：': std8_low_area_lst, 'batch：': batch_lst},
            index=None)
        print(df)
        print("——" * 50)
        return com_lst

    def get_std_zero_conc_compound(self):
        batch_set = set(self.batch_std_dict)
        com_lst = []
        for compound in self.conc_dict.keys():
            for batch in batch_set:
                batch_std_lst = self.batch_std_dict[batch]
                std_conc_lst = [self.conc_dict[compound][std]
                                for std in batch_std_lst]
                std_02_conc_lst = std_conc_lst[:3]
                std_38_conc_lst = std_conc_lst[3:]
                num02 = std_02_conc_lst.count(0)
                num38 = std_38_conc_lst.count(0)
                if num02 <= 1 and num38 >= 2:
                    if compound not in com_lst:
                        com_lst.append(compound)
        return com_lst

    def detect_std7_std8_ratio1(self):
        std7_std8_low_ratio_com_lst, ratio_lst = [], []
        std7_conc_lst, std8_conc_lst = [], []
        batch_lst = []
        com_lst = []
        dict1 = {}
        for batch in self.batch_std_dict:
            std_lst = self.batch_std_dict[batch]
            dict1[batch] = {}
            for std in std_lst:
                if "_std8" in std.lower() or "_std_8" in std.lower():
                    dict1[batch]['std8'] = std
                elif "_std7" in std.lower() or "_std_7" in std.lower():
                    dict1[batch]['std7'] = std
        for batch in dict1:
            std8 = dict1[batch]['std8']
            std7 = dict1[batch]['std7']
            for com in self.compound_lst:
                com_index = self.com_index_dict[com]
                if "IS" != com_index[:2]:
                    if self.conc_dict[com][std8] != 0:
                        ratio = self.conc_dict[com][std7] / \
                                self.conc_dict[com][std8]
                        if ratio < 0.35:
                            std7_std8_low_ratio_com_lst.append(
                                self.com_index_dict[com])
                            ratio_lst.append(ratio)
                            std7_conc_lst.append(self.conc_dict[com][std7])
                            std8_conc_lst.append(self.conc_dict[com][std8])
                            batch_lst.append(batch)
                            com_lst.append(com)

        print('ratio cutoff:', 0.35)
        df = pd.DataFrame({"index": std7_std8_low_ratio_com_lst,
                           'compound': com_lst,
                           "conc_std7/conc_std8": ratio_lst,
                           "conc_std7": std7_conc_lst,
                           "conc_sdt8": std8_conc_lst,
                           "batch": batch_lst})
        print(df)
        print("——" * 50)

        return com_lst

    def detect_std8_std0_ratio(self):
        std8_std0_low_ratio_com_lst, ratio_lst = [], []
        std0_area_lst, std8_area_lst = [], []
        batch_lst, com_lst = [], []
        dict1 = {}
        for batch in self.batch_std_dict:
            std_lst = self.batch_std_dict[batch]
            dict1[batch] = {}
            for std in std_lst:
                if "_std8" in std.lower() or "_std_8" in std.lower():
                    dict1[batch]['std8'] = std
                elif "_std0" in std.lower() or "_std_0" in std.lower():
                    dict1[batch]['std0'] = std

        for batch in dict1:
            std8 = dict1[batch]['std8']
            std0 = dict1[batch]['std0']

            for com in self.compound_lst:
                com_index = self.com_index_dict[com]
                if "IS" != com_index[:2]:
                    std8_area = self.intensity_dict[com][std8]
                    std0_area = self.intensity_dict[com][std0]
                    if self.intensity_dict[com][std0] != 0:
                        ratio = std8_area / std0_area
                        if ratio < 10:
                            std8_std0_low_ratio_com_lst.append(com_index)
                            ratio_lst.append(round(ratio, 2))
                            std0_area_lst.append(int(std0_area))
                            std8_area_lst.append(int(std8_area))
                            batch_lst.append(batch)
                            com_lst.append(com)

        print('std8/std0 ratio cutoff:', 10)
        df = pd.DataFrame({"index": std8_std0_low_ratio_com_lst, 'compound': com_lst, "area_std8/area_std0": ratio_lst,
                           "area_std8": std8_area_lst, "area_std0": std0_area_lst, "batch": batch_lst})
        print(df)
        print("——" * 50)
        return com_lst


def divide_lst(total_lst, n):
    num = math.ceil(len(total_lst) / n)
    lst = []
    for i in range(n):
        sub_lst = total_lst[num * i:num * (i + 1)]
        lst.append(sub_lst)
    return lst


def smooth(intensity_list, n, m):
    # n:连续点的数量
    # m:迭代次数
    # 返回平滑后的强度
    j = 1
    try:
        while j <= m:
            intensity_list = savgol_filter(intensity_list, n, 4)
            j += 1
        return list(intensity_list)
    except BaseException:
        return list(intensity_list)


def get_local_min(intensity_list, time_list):
    # n:连续点的数量
    # m:迭代次数
    local_mins = []
    if len(intensity_list) > 2:
        for i in range(1, len(intensity_list) - 1):
            if intensity_list[i] < intensity_list[i + 1] and intensity_list[i] <= intensity_list[i - 1] or \
                    intensity_list[i] <= intensity_list[i + 1] and intensity_list[i] < intensity_list[i - 1]:
                local_mins.append((time_list[i], intensity_list[i]))
        local_mins.insert(0, (time_list[0], intensity_list[0]))
        local_mins.append((time_list[-1], intensity_list[-1]))
        return local_mins
    return local_mins


def get_local_max(intensity_list, time_list):
    # n:连续点的数量
    # m:迭代次数
    local_maxs = []
    if len(intensity_list) > 2:
        x = int(len(intensity_list) * 0.2)  # 噪音强度是后百分之20的均值
        noise = np.mean(sorted(intensity_list)[:x])
        smooth_intensity_list = intensity_list
        for i in range(1, len(smooth_intensity_list) - 1):
            if smooth_intensity_list[i] > smooth_intensity_list[i + 1] and smooth_intensity_list[i] >= \
                    smooth_intensity_list[i - 1] or \
                    smooth_intensity_list[i] >= smooth_intensity_list[i + 1] and smooth_intensity_list[i] > \
                    smooth_intensity_list[i - 1]:
                if intensity_list[i] >= 3 * noise:  # 三倍信噪比
                    local_maxs.append((time_list[i], smooth_intensity_list[i]))
        local_maxs = sorted(local_maxs, key=lambda _x: _x[1], reverse=True)
        return local_maxs
    return local_maxs


class DrawPic(object):
    def __init__(self, compound_list_excel=None, peak_list=None, conc_list=None, intensity_list=None, raw_dir=None,
                 saved_pdf_name=None, cpu_core_num=None, rm_dir=True, small_color='purple', big_color='#00F200',
                 color_file=None, color_group=None, k_cutoff=0.7, modified_pdf=None,
                 modified_conc_file=None):
        self.modified_pdf = modified_pdf
        self.modified_conc_file = modified_conc_file
        self.compound_list_excel = compound_list_excel
        df = pd.read_excel(self.compound_list_excel)
        compound_lst, index_lst = list(df['Compound']), list(df['Index'])
        self.compound_lst = compound_lst
        self.compound_index_dict = dict(zip(compound_lst, index_lst))

        self.intensity_list = intensity_list
        self.sample_order_lst = list(pd.read_excel(self.intensity_list)['sample'])
        self.drop_compound = []
        self.not_detect_compounds = []
        self.k_cutoff = k_cutoff
        self.saved_pdf_name = saved_pdf_name
        self.cpu_core_num = cpu_core_num
        self.rm_dir = rm_dir
        self.small_color = small_color
        self.big_color = big_color
        current_dir = os.path.dirname(os.path.abspath(__file__))

        curr_time = datetime.datetime.now()
        out_dir = datetime.datetime.strftime(curr_time, '%Y-%m-%d_%H-%M-%S')

        self.color_dict = read_color_info(color_file, color_group)

        self.out_path = os.path.join(current_dir, out_dir)
        os.makedirs(self.out_path)

        file_lst = os.listdir(raw_dir)
        file_list = [os.path.join(raw_dir, txt) for txt in file_lst]

        a = GetInfoDict(compound_list=compound_list_excel, peak_list=peak_list, conc_list=conc_list,
                        file_list=file_list,
                        intensity_list=intensity_list)
        self.std_zero_conc_compound_lst = a.get_std_zero_conc_compound()
        self.com_rt_dict = a.com_rt_dict
        # self.compound_range_rsd_dict = a.get_com_range_rsd_dict()
        self.compound_batch_std_respond_dict = a.get_compound_batch_std_respond_dict()
        self.intensity_dict = a.intensity_dict
        self.std8_low_area_com_lst = a.detect_std8_area()
        self.std8_std0_low_ratio_com_lst = a.detect_std8_std0_ratio()
        self.std_coef_compound_lst = a.detect_std_coef(self.k_cutoff)
        self.conc_dict = a.get_conc_dict()
        self.sample_std8_dict = a.get_std8_dict()
        self.range_dict = a.get_range_dict()
        self.peak_dict = a.get_peak_dict()
        self.rt_dict = a.get_rt_dict()
        self.index_dict = a.get_index_dict()
        self.peak_data = pd.read_excel('peaks.xlsx', index_col='sample')
        self.sample_lst = list(self.peak_data.index)

        self.com_IS_dict = a.com_IS_dict
        self.com_index_dict = a.com_index_dict

        self.batch_std_dict = a.get_batch_std_dict()
        self.batch_sample_dict = a.get_batch_sample_dict()

        dict1 = self.detect_compounds()
        self.high_conc_compounds = dict1['high']
        self.low_conc_compounds = dict1['low']
        self.std_problem_compounds = dict1['std_error']

    def draw(self, compound, out_path):
        out_pdf = compound + ".pdf"
        out_pdf = os.path.join(out_path, out_pdf)
        with PdfPages(out_pdf) as pdf:
            length = len(self.sample_lst)
            if length > 0:
                page = math.ceil(length / 20)
                for nnn in range(page):
                    txt_lsts = self.sample_lst[nnn * 20:(nnn + 1) * 20]
                    self.draw_multi(txt_lsts, compound, pdf)
        self.calculated_msfile.append(1)

    def draw_multi(self, txt_lst, compound, pdf):
        none_lst = [
            txt for txt in txt_lst if self.range_dict[compound][txt]['left'] is None]
        num = len(none_lst)
        compound_index = self.index_dict[compound].split('_')[0]
        fig, ax = plt.subplots(
            nrows=10, ncols=2, sharex=True, figsize=(
                8.8, 4.8), dpi=220)
        fig.subplots_adjust(hspace=0)
        title = compound_index + "   " + compound + "   " + str(num)
        color = 'black'
        if self.color_dict:
            if compound in self.color_dict:
                conc = self.color_dict[compound]['conc']
                title = str(conc) + "   " + compound_index + \
                        "   " + compound + "   " + str(num)
                color = self.color_dict[compound]['color']
        fig.suptitle(title, color=color)

        if compound in self.std_problem_compounds:
            fig.supxlabel("**********", color='blue', fontsize=16)
        elif compound in self.high_conc_compounds:
            fig.supxlabel("**********", color='red', fontsize=16)
        elif compound in self.low_conc_compounds:
            fig.supxlabel("**********", color='green', fontsize=16)
        else:
            fig.supxlabel("**********", color='black', fontsize=16)

        # ax.set_title(title, fontproperties=font, color='r', size=16)

        if len(txt_lst) <= 10:
            for idx, txt in enumerate(txt_lst):
                sub_ax = ax[idx][0]
                self.draw_single(txt, compound, sub_ax)
            for idx in range(len(txt_lst), 10):
                sub_ax = ax[idx][0]
                sub_ax.remove()
            for idx in range(10):
                sub_ax = ax[idx][1]
                sub_ax.remove()
        else:
            sub_lst1 = txt_lst[:10]
            sub_lst2 = txt_lst[10:]
            for idx, txt in enumerate(sub_lst1):
                sub_ax = ax[idx][0]
                self.draw_single(txt, compound, sub_ax)
            for idx, txt in enumerate(sub_lst2):
                sub_ax = ax[idx][1]
                self.draw_single(txt, compound, sub_ax)
            for idx in range(len(sub_lst2), 10):
                sub_ax = ax[idx][1]
                sub_ax.remove()
        plt.margins(0, 0)
        pdf.savefig(fig)
        plt.clf()

    def std_check(self, compound, txt):
        time_list, intensity_list = self.peak_dict[compound][txt]['time'], self.peak_dict[compound][txt][
            'intensity']
        std8 = self.sample_std8_dict[txt]
        std8_left, std8_right = self.range_dict[compound][std8]['left'], \
                                self.range_dict[compound][std8]['right']
        left, right = self.range_dict[compound][txt]['left'], self.range_dict[compound][txt]['right']
        if std8_left is None:
            return True
        else:
            # 如果没有左区间
            if left is None:
                narrowed_intensity_list, narrowed_time_list = narrow_range(intensity_list, time_list, std8_left,
                                                                           std8_right)
                std = np.std(narrowed_intensity_list, ddof=1)
                ave = np.mean(narrowed_intensity_list)
                rsd = std / ave

                if rsd > 0.3 and ave > 5000:
                    return True
            else:
                narrowed_intensity_list, narrowed_time_list = narrow_range(
                    intensity_list, time_list, left, right)
                std8_time_list, std8_intensity_list = self.peak_dict[compound][std8]['time'], \
                                                      self.peak_dict[compound][std8][
                                                          'intensity']

                std8_narrowed_intensity_list, std8_narrowed_time_list = narrow_range(std8_intensity_list,
                                                                                     std8_time_list, std8_left,
                                                                                     std8_right)

                std8_noise = calculate_noise(std8_intensity_list)
                std8_sn = max(std8_narrowed_intensity_list) / std8_noise

                peak_max = max(intensity_list)

                peak_max_time = None
                for idx, i in enumerate(intensity_list):
                    if i == peak_max:
                        peak_max_time = time_list[idx]

                local_peak_max = max(narrowed_intensity_list)

                if max(std8_narrowed_intensity_list) == max(std8_intensity_list) and \
                        std8_sn > 10 and \
                        local_peak_max < 0.05 * peak_max and \
                        abs(peak_max_time - self.range_dict[compound][txt]['rt']) < 0.15 and \
                        peak_max > 1000:
                    return True
        return False

    def get_low_conc_compound_lst(self):
        com_lst = []
        for compound in self.compound_lst:
            conc_lst = []
            for txt in self.sample_lst:
                conc = self.conc_dict[compound][txt]
                if 'std' not in txt.lower() and 'blank' not in txt.lower():
                    conc_lst.append(conc)
                    median_conc = np.median(conc_lst)
                    if median_conc < 0.1:
                        if compound not in com_lst:
                            com_lst.append(compound)
        return com_lst

    def get_zero_conc_compound_lst(self):
        com_lst = []
        for compound in self.compound_lst:
            conc_lst = []
            for txt in self.sample_lst:
                conc = self.conc_dict[compound][txt]
                if 'std' not in txt.lower():
                    conc_lst.append(conc)
            num = conc_lst.count(0)
            ratio = num / len(conc_lst)
            if ratio >= 0.3:
                com_lst.append(compound)
        return com_lst

    def detect_compounds(self):
        low_range_shift_com_lst, high_range_shift_com_lst = self.check_sample_range()
        high_com_lst = high_range_shift_com_lst
        low_com_lst = self.get_zero_conc_compound_lst()
        for i in low_range_shift_com_lst:
            if i not in low_com_lst:
                low_com_lst.append(i)
        std_error_com_lst = self.std_coef_compound_lst

        for i in self.std_zero_conc_compound_lst:
            if i not in std_error_com_lst:
                std_error_com_lst.append(i)

        low_conc_com_lst = self.get_low_conc_compound_lst()
        for i in low_conc_com_lst:
            if i not in low_com_lst:
                low_com_lst.append(i)

        for i in self.std8_low_area_com_lst:
            if i not in std_error_com_lst:
                std_error_com_lst.append(i)

        for i in self.std8_std0_low_ratio_com_lst:
            if i not in std_error_com_lst:
                std_error_com_lst.append(i)

        for compound in self.compound_lst:
            conc1_lst, area1_lst, height1_lst = [], [], [0]
            conc2_lst, area2_lst, height2_lst = [], [], [0]
            sn_lst = []
            for txt in self.sample_lst:
                time_list, intensity_list = self.peak_dict[compound][txt]['time'], self.peak_dict[compound][txt][
                    'intensity']
                rt_ref = self.rt_dict[compound]
                left, right = self.range_dict[compound][txt]['left'], self.range_dict[compound][txt]['right']
                std8 = self.sample_std8_dict[txt]
                std8_left, std8_right = self.range_dict[compound][std8]['left'], \
                                        self.range_dict[compound][std8]['right']

                for i in [3, 4, 5, 6, 7, 8]:
                    std_name = 'std' + str(i)
                    if std_name in txt.lower():
                        if self.std_check(compound, txt):
                            if compound not in std_error_com_lst:
                                std_error_com_lst.append(compound)
                if 'std8' in txt.lower():
                    if not self.peak_ok(intensity_list, time_list, left, right):
                        self.reset_std8_range(compound, txt)

                if 'std' not in txt.lower() and '_blank_' not in txt.lower():
                    std = np.std(intensity_list, ddof=1)
                    ave = np.mean(intensity_list)
                    rsd = std / ave

                    if left:
                        narrowed_intensity_list, narrowed_time_list = narrow_range(intensity_list, time_list, left,
                                                                                   right)
                        x = int(len(intensity_list) * 0.2)
                        noise = np.mean(sorted(intensity_list)[:x])
                        sn = max(narrowed_intensity_list) / noise
                        sn_lst.append(sn)
                        conc = self.conc_dict[compound][txt]
                        conc1_lst.append(conc)
                        area = calculate_area(
                            intensity_list, time_list, left, right)
                        area1_lst.append(area)
                        height1_lst.append(max(narrowed_intensity_list))

                        ave2 = np.mean(narrowed_intensity_list)
                        std2 = np.std(narrowed_intensity_list, ddof=1)
                        rsd2 = std2 / ave2

                        if rsd < 0.6 and rsd2 < 0.6:
                            if compound not in low_com_lst:
                                low_com_lst.append(compound)
                    else:
                        conc2_lst.append(0)
                        area = calculate_area(
                            intensity_list, time_list, std8_left, std8_right)
                        area2_lst.append(area)

                        if std8_left:
                            narrowed_intensity_list, narrowed_time_list = narrow_range(intensity_list, time_list,
                                                                                       std8_left,
                                                                                       std8_right)
                            ave2 = np.mean(narrowed_intensity_list)
                            std2 = np.std(narrowed_intensity_list, ddof=1)
                            rsd2 = std2 / ave2

                            height2_lst.append(max(narrowed_intensity_list))

                            if rsd2 > 1:
                                if compound not in low_com_lst:
                                    low_com_lst.append(compound)

            if len(area1_lst) > 0 and len(area2_lst) > 0:
                if max(area2_lst) > np.mean(
                        area1_lst) and np.mean(conc1_lst) > 3:
                    if compound not in high_com_lst:
                        high_com_lst.append(compound)
                elif max(height2_lst) > 1000 and np.mean(conc1_lst) >= 3:
                    if compound not in high_com_lst:
                        high_com_lst.append(compound)
                elif max(height2_lst) > 1000 and np.mean(conc1_lst) <= 3:
                    if compound not in low_com_lst:
                        low_com_lst.append(compound)

        self.compound_color_dict = {}
        for compound in self.compound_lst:
            if compound in std_error_com_lst:
                color = (0, 0, 255)

            elif compound in high_com_lst:
                color = (255, 0, 0)

            elif compound in low_com_lst:
                color = (0, 255, 0)
            else:
                color = (0, 0, 0)
            self.compound_color_dict[compound] = color

        simplify_compound_name_dict = {simplify_compound_name(i): i for i in self.compound_color_dict}

        for compound in compound_input:
            name1 = simplify_compound_name(compound)
            if name1 in simplify_compound_name_dict:
                compound = simplify_compound_name_dict[name1]

                if self.compound_color_dict[compound] == (0, 0, 0):
                    self.compound_color_dict[compound] = (255, 0, 0)

        low_conc_color_rgb = (51, 0, 102)
        for compound in self.compound_lst:
            if compound in low_conc_com_lst and compound not in high_com_lst and compound not in std_error_com_lst:
                color = low_conc_color_rgb
                self.compound_color_dict[compound] = color

        self.color_compound_lst = []
        for com in self.compound_color_dict:
            color = self.compound_color_dict[com]
            if color in [(255, 0, 0), (0, 255, 0), low_conc_color_rgb]:
                self.color_compound_lst.append(com)
            else:
                if color == (0, 0, 255) and (com in high_com_lst or com in low_com_lst or com in low_conc_com_lst):
                    self.color_compound_lst.append(com)

        com_detect_info_dict = {
            'high': high_com_lst,
            'low': low_com_lst,
            'std_error': std_error_com_lst}
        return com_detect_info_dict

    def check_sample_range(self):
        low_lst, high_lst = [], []
        for compound in self.compound_lst:
            conc_lst = []
            for txt in self.sample_order_lst:
                if 'blank' not in txt.lower():
                    time_list, intensity_list = self.peak_dict[compound][txt]['time'], self.peak_dict[compound][txt][
                        'intensity']
                    rt_ref = self.rt_dict[compound]
                    left, right = self.range_dict[compound][txt]['left'], self.range_dict[compound][txt]['right']
                    std8 = self.sample_std8_dict[txt]
                    std8_left, std8_right = self.range_dict[compound][std8]['left'], \
                                            self.range_dict[compound][std8]['right']
                    std8_time_list, std8_intensity_list = self.peak_dict[compound][std8]['time'], \
                                                          self.peak_dict[compound][std8][
                                                              'intensity']
                    if left and std8_left:
                        area = calculate_area(intensity_list, time_list, left, right)
                        # if self.peak_ok(std8_intensity_list, std8_time_list, std8_left, std8_right):
                        range_diff = (abs(left - std8_left) + abs(right - std8_right)) / 2

                        if range_diff > 0.1 and area > 500:
                            # print(txt, compound, range_diff, range_diff/abs(std8_right-std8_left))
                            conc = self.conc_dict[compound][txt]
                            conc_lst.append(conc)
            if len(conc_lst) > 0:
                aver_conc = np.average(conc_lst)
                if aver_conc >= 3:
                    high_lst.append(compound)
                else:
                    low_lst.append(compound)

        return low_lst, high_lst

    def draw_single(self, txt, compound, sub_ax):
        time_list, intensity_list = self.peak_dict[compound][txt][
                                        'time'], self.peak_dict[compound][txt]['intensity']
        rt_ref = self.rt_dict[compound]
        time_list, intensity_list = np.array(
            time_list), np.array(intensity_list)
        time_range = time_list[-1] - time_list[0]
        sub_ax.set_xlim(time_list[0] - 0.3 * time_range, time_list[-1])
        left, right = self.range_dict[compound][txt]['left'], self.range_dict[compound][txt]['right']

        rt = self.range_dict[compound][txt]['rt']
        sub_ax.plot(time_list, intensity_list, linewidth=0.5)
        sub_ax.set_ylim(0.01, )
        sub_ax.tick_params(
            axis='both',
            which='both',
            bottom=True,
            left=False,
            labelbottom=True,
            labelleft=False)
        conc = self.conc_dict[compound][txt]
        if '.' in str(conc):
            conc = "{:.3}".format(conc)

        max_value = int(max(intensity_list))
        if max_value > 10000:
            max_value = '%.2e' % max(intensity_list)

        axes1 = sub_ax.inset_axes([0.01, 0.3, .2, 0.6], )
        axes1.tick_params(
            axis='both',
            which='both',
            bottom=False,
            left=False,
            labelbottom=False,
            labelleft=False)
        axes1.spines['bottom'].set_linewidth(0.5)  # 图框下边
        axes1.spines['left'].set_linewidth(0.5)  # 图框左边
        axes1.spines['top'].set_linewidth(0.5)  # 图框上边
        axes1.spines['right'].set_linewidth(0.5)  # 图框右边

        sub_ax.axvline(rt_ref, color='gray', linestyle=":", linewidth=0.5)

        if left:
            narrowed_intensity_list, narrowed_time_list = narrow_range(
                intensity_list, time_list, left, right)
            axes1.plot(
                narrowed_time_list,
                narrowed_intensity_list,
                linewidth=0.2)
            axes1.fill_between(narrowed_time_list, narrowed_intensity_list,
                               where=(
                                             left < narrowed_time_list) & (
                                             narrowed_time_list < right),
                               facecolor=self.small_color)
            axes1.axvline(rt_ref, color='gray', linestyle=":", linewidth=0.5)
            sub_ax.axvline(rt, color='b', linestyle=":", linewidth=0.5)
            sub_ax.axvline(rt_ref, color='gray', linestyle=":", linewidth=0.5)
            sub_ax.axvline(left, color='r', linestyle=":", linewidth=0.5)
            sub_ax.axvline(right, color='r', linestyle=":", linewidth=0.5)

            sub_ax.fill_between(time_list, intensity_list, where=(left < time_list) & (time_list < right),
                                facecolor=self.big_color)
            sub_ax.text(0.98, 0.9, txt, transform=sub_ax.transAxes, horizontalalignment='right',
                        verticalalignment='top',
                        size=8, color='black')
            height = max(narrowed_intensity_list)
            sub_ax.text(0.4, 0.4, str(int(height)), transform=sub_ax.transAxes, horizontalalignment='right',
                        verticalalignment='top', size=8, color='black')
        else:
            axes1.spines['bottom'].set_color("red")  # 图框下边
            axes1.spines['left'].set_color("red")  # 图框左边
            axes1.spines['top'].set_color("red")  # 图框上边
            axes1.spines['right'].set_color("red")  # 图框右边

            std8 = self.sample_std8_dict[txt]
            std8_left, std8_right = self.range_dict[compound][std8][
                                        'left'], self.range_dict[compound][std8]['right']
            rt_std8 = self.range_dict[compound][std8]['rt']
            sub_ax.text(0.98, 0.9, txt, transform=sub_ax.transAxes, horizontalalignment='right',
                        verticalalignment='top',
                        size=8, color='red')

            if std8_left:
                sub_ax.axvline(
                    std8_left,
                    color='r',
                    linestyle=":",
                    linewidth=0.5)
                sub_ax.axvline(
                    std8_right,
                    color='r',
                    linestyle=":",
                    linewidth=0.5)
                sub_ax.axvline(
                    rt_std8,
                    color='blue',
                    linestyle=":",
                    linewidth=0.5)

                narrowed_intensity_list, narrowed_time_list = narrow_range(intensity_list, time_list, std8_left,
                                                                           std8_right)
                axes1.plot(
                    narrowed_time_list,
                    narrowed_intensity_list,
                    linewidth=0.2)

                axes1.axvline(
                    std8_left,
                    color='red',
                    linestyle=":",
                    linewidth=0.5)
                axes1.axvline(
                    std8_right,
                    color='red',
                    linestyle=":",
                    linewidth=0.5)
                axes1.axvline(
                    rt_std8,
                    color='blue',
                    linestyle=":",
                    linewidth=0.5)
                height = max(narrowed_intensity_list)
                sub_ax.text(0.4, 0.4, str(int(height)), transform=sub_ax.transAxes, horizontalalignment='right',
                            verticalalignment='top', size=8, color='black')

        if str(conc) != '0.0':
            sub_ax.text(0.98, 0.4, str(conc), transform=sub_ax.transAxes, size=8, color='black',
                        horizontalalignment='right', verticalalignment='top')
        else:
            sub_ax.text(0.98, 0.4, "0", transform=sub_ax.transAxes, size=8, color='black', horizontalalignment='right',
                        verticalalignment='top')
        sub_ax.text(-0.01, 0.8, str(max_value), transform=sub_ax.transAxes, size=8, color='black',
                    horizontalalignment='right', verticalalignment='top')

    def reset_low_conc_compound_conc(self, compound_list):
        # conc_dict = {compound: {} for compound in compound_list}

        all_sample_lst = self.sample_order_lst

        for compound in compound_list:
            for batch in self.batch_std_dict:
                batch_sample_lst = self.batch_sample_dict[batch]
                batch_std_lst = self.batch_std_dict[batch]
                all_lst = batch_std_lst + batch_sample_lst
                std_conc_lst = [self.conc_dict[compound][std]
                                for std in batch_std_lst]
                std_responds_lst = []
                for std_sample in batch_std_lst:
                    respond = self.get_compound_respond(compound, std_sample)
                    std_responds_lst.append(respond)

                k, b = force_zero_k(std_conc_lst, std_responds_lst), 0
                coef = polyfot(std_conc_lst, std_responds_lst, k, b)

                if coef < 0.9:
                    # print(compound, coef)
                    k = std_responds_lst[-1] / std_conc_lst[-1]

                for txt in all_lst:
                    respond = self.get_compound_respond(compound, txt)
                    if k != 0:
                        conc = respond / k
                    else:
                        conc = 0
                    self.conc_dict[compound][txt] = conc
        all_sample_lst.insert(0, '')
        dict1 = {'sample': all_sample_lst}
        for compound in compound_list:
            index_name = self.index_dict[compound]
            com_conc_lst = []
            for sample in all_sample_lst[1:]:
                conc = self.conc_dict[compound][sample]
                com_conc_lst.append(conc)
            com_conc_lst.insert(0, index_name)
            dict1[compound] = com_conc_lst
        df = pd.DataFrame(dict1)
        if '.csv' in self.modified_conc_file:
            df.to_csv(self.modified_conc_file, index=None)
        else:
            df.to_excel(self.modified_conc_file, index=None)
        return df

    def get_compound_respond(self, compound, txt):
        IS = self.com_IS_dict[compound]

        intensity = self.intensity_dict[compound][txt]
        IS_intensity = self.intensity_dict[IS][txt]
        if compound == IS:
            respond = intensity
        else:
            if IS_intensity != 0:
                respond = intensity / IS_intensity
            else:
                respond = 0
        return respond

    def get_range(self, intensity_list, time_list, rt, n, m):
        '''
        根据输入RT，判断它在哪个峰的范围内
        '''
        left, right = time_list[0], time_list[-1]
        if len(intensity_list) < 3:
            return None
        else:
            smooth_intensity_list = smooth(intensity_list, n, m)
            local_mins = get_local_min(smooth_intensity_list, time_list)
            local_mins = sorted(local_mins, key=lambda x: x[0])
            rt_lst, int_lst = [i[0] for i in local_mins], [i[1] for i in local_mins]
            if len(local_mins) > 2:
                if rt >= right or rt <= left:
                    length = int(len(time_list) * 0.5)
                    ttt = time_list[length]
                    return [ttt - 0.1, ttt + 0.1]
                else:
                    for idx, i in enumerate(list(zip(rt_lst, rt_lst[1:]))):
                        left_time, right_time = i[0], i[1]
                        if left_time <= rt <= right_time:
                            return [left_time, right_time]

            else:
                if rt - 0.1 < left:
                    left_time = left
                else:
                    left_time = rt - 0.1
                if rt + 0.1 > right:
                    right_time = right
                else:
                    right_time = rt + 0.1
                return [left_time, right_time]

    def reset_std8_range(self, compound, std8):
        std8_left, std8_right = self.range_dict[compound][std8]['left'], \
                                self.range_dict[compound][std8]['right']
        rt = self.com_rt_dict[compound]

        std8_time_list, std8_intensity_list = self.peak_dict[compound][std8]['time'], self.peak_dict[compound][std8][
            'intensity']
        if std8_left:
            for i in [9, 13, 17, 19, 25, 30, 50, 100]:
                new_left, new_right = self.get_range(std8_intensity_list, std8_time_list, rt, i, 11)
                if self.peak_ok(std8_intensity_list, std8_time_list, new_left, new_right):
                    self.range_dict[compound][std8]['left'], self.range_dict[compound][std8][
                        'right'] = new_left, new_right
                    area = calculate_area(
                        std8_intensity_list, std8_time_list, new_left, new_right)
                    self.intensity_dict[compound][std8] = area
                    return

    def peak_ok(self, intensity_list, time_list, time_left, time_right):
        if time_left:
            narrowed_intensity_list = narrow_range(intensity_list, time_list, time_left, time_right)[0]
            time_left_abs_lst = [abs(i - time_left) for i in time_list]
            time_right_abs_lst = [abs(i - time_right) for i in time_list]
            min_left_abs_diff = min(time_left_abs_lst)
            idx1 = time_left_abs_lst.index(min_left_abs_diff)
            min_right_abs_diff = min(time_right_abs_lst)
            idx2 = time_right_abs_lst.index(min_right_abs_diff)
            left_intensity = intensity_list[idx1]
            right_intensity = intensity_list[idx2]
            if len(narrowed_intensity_list) == 0:
                return False
            else:
                if left_intensity <= 0.05 * max(narrowed_intensity_list) and right_intensity <= 0.05 * max(
                        narrowed_intensity_list) \
                        and max(narrowed_intensity_list) > 5000:
                    return True
                else:
                    return False
        else:
            return False

    def peak_bad(self, intensity_list, time_list, time_left, time_right):
        if time_left:
            narrowed_intensity_list = narrow_range(intensity_list, time_list, time_left, time_right)[0]
            time_left_abs_lst = [abs(i - time_left) for i in time_list]
            time_right_abs_lst = [abs(i - time_right) for i in time_list]
            min_left_abs_diff = min(time_left_abs_lst)
            idx1 = time_left_abs_lst.index(min_left_abs_diff)
            min_right_abs_diff = min(time_right_abs_lst)
            idx2 = time_right_abs_lst.index(min_right_abs_diff)
            left_intensity = intensity_list[idx1]
            right_intensity = intensity_list[idx2]
            if len(narrowed_intensity_list) == 0:
                return False
            else:
                if ((left_intensity >= 0.5 * max(narrowed_intensity_list) and  right_intensity <= 0.1 * max(narrowed_intensity_list)) or (left_intensity <= 0.1 * max(narrowed_intensity_list) and right_intensity >= 0.5 * max(
                        narrowed_intensity_list))) and max(narrowed_intensity_list) > 5000:
                    return True
                else:
                    return False
        else:
            return False


    def reset_std8_large_range(self):
        self.com_std8_range_dict = {}
        for compound in self.compound_lst:
            for txt in self.sample_lst:
                if '_std8' in txt.lower() or '_std_8' in txt.lower():
                    std8_left, std8_right = self.range_dict[compound][txt]['left'], \
                                            self.range_dict[compound][txt]['right']
                    if std8_left:
                        std8_rt = self.range_dict[compound][txt]['rt']
                        std8_time_list, std8_intensity_list = self.peak_dict[compound][txt]['time'], \
                                                              self.peak_dict[compound][txt][
                                                                  'intensity']
                        abs_left_distance, abs_right_distance = abs(
                            std8_left - std8_rt), abs(std8_right - std8_rt)
                        min_d = min(abs_left_distance, abs_right_distance)
                        new_left, new_right = std8_rt - min_d, std8_rt + min_d
                        std8_area = calculate_area(
                            std8_intensity_list, std8_time_list, new_left, new_right)
                        d_lst = list(np.arange(0.1, 0.4, 0.01) * min_d)
                        d_95_lst = []
                        for idx, i in enumerate(d_lst):
                            left_value = std8_rt - i
                            right_value = std8_rt + i
                            aarea = calculate_area(
                                std8_intensity_list, std8_time_list, left_value, right_value)
                            if compound == 'L-Alanine':
                                if aarea >= 0.95 * std8_area:
                                    d_95_lst.append(i)
                            else:
                                if aarea >= 0.95 * std8_area and self.peak_ok(std8_intensity_list, std8_time_list,
                                                                              std8_left, std8_right):
                                    d_95_lst.append(i)

                        if len(d_95_lst) > 0:
                            self.com_std8_range_dict[(compound, txt)] = (
                                std8_rt - max(d_95_lst), std8_rt + max(d_95_lst))

    def reset_low_conc_compound_range(self, compound_list):
        self.reset_std8_large_range()
        lst = []
        for compound in compound_list:
            for txt in self.sample_lst:
                std8 = self.sample_std8_dict[txt]
                std8_left, std8_right = self.range_dict[compound][std8]['left'], \
                                        self.range_dict[compound][std8]['right']
                if (compound, std8) in self.com_std8_range_dict:
                    std8_left, std8_right = self.com_std8_range_dict[(compound, std8)]

                std8_rt = self.range_dict[compound][std8]['rt']
                if std8_left:
                    abs_left_distance, abs_right_distance = abs(
                        std8_left - std8_rt), abs(std8_right - std8_rt)
                    min_d = min(abs_left_distance, abs_right_distance)
                    new_left, new_right = std8_rt - min_d, std8_rt + min_d
                    time_list, intensity_list = self.peak_dict[compound][txt]['time'], self.peak_dict[compound][txt][
                        'intensity']

                    area = calculate_area(
                        intensity_list, time_list, new_left, new_right)

                    self.intensity_dict[compound][txt] = area
                    self.range_dict[compound][txt]['left'] = new_left
                    self.range_dict[compound][txt]['right'] = new_right
                    self.range_dict[compound][txt]['rt'] = std8_rt

                    if self.peak_bad(intensity_list, time_list, new_left, new_right):
                        name = self.index_dict[compound]
                        if name not in lst:
                            lst.append(name)
        print(lst)

    def redraw_compound_list(self, compound_list):
        self.reset_low_conc_compound_range(compound_list)
        for compound in compound_list:
            self.draw(compound, self.out_path)

    def draw_compound_list(self, compound_list, out_path):
        for compound in compound_list:
            self.draw(compound, out_path)

            calculated_msfile_num = len(self.calculated_msfile)
            if calculated_msfile_num in self.num_lst:
                percent = round(calculated_msfile_num /
                                len(self.all_msfile) * 100, 2)
                string_line = str(percent) + "%"
                time_line = time.strftime(
                    '%Y-%m-%d %H:%M:%S',
                    time.localtime(
                        time.time()))
                print(time_line + " " * (11 - len(string_line)) + string_line)

    def multi_core_draw(self, compound_lst, out_path):
        import multiprocessing
        from tqdm import tqdm
        self.calculated_msfile = multiprocessing.Manager().list()
        self.all_msfile = multiprocessing.Manager().list()
        self.num_lst = split_num(len(compound_lst), 5)
        for i in compound_lst:
            self.all_msfile.append(i)
        pbar = tqdm(total=len(compound_lst))
        pbar.set_description('Run')
        update = lambda *args: pbar.update()

        n_proc = 10
        pool = multiprocessing.Pool(n_proc)
        for com in compound_lst:
            pool.apply_async(self.draw, (com, out_path), callback=update)
        pool.close()
        pool.join()

    def multi_core_draw_2(self, compound_lst, out_path):
        import multiprocessing
        self.calculated_msfile = multiprocessing.Manager().list()
        self.all_msfile = multiprocessing.Manager().list()
        self.num_lst = split_num(len(compound_lst), 5)
        for i in compound_lst:
            self.all_msfile.append(i)

        compound_split_lst = divide_lst(compound_lst, self.cpu_core_num)
        import multiprocessing
        jobs = [multiprocessing.Process(target=self.draw_compound_list, args=(x, out_path,)) for x in
                compound_split_lst]
        for i in jobs:
            i.start()
        for i in jobs:
            i.join()

    def multi_job(self):
        time_line = time.strftime(
            '%Y-%m-%d %H:%M:%S',
            time.localtime(
                time.time()))
        print("——" * 50)
        print('process:')
        string_line = "0.00%"
        print(time_line + " " * (11 - len(string_line)) + string_line)
        #self.out_path = '2024-10-31_10-30-37'
        self.multi_core_draw_2(self.compound_lst, self.out_path)
        self.combine_pdf(compound_lst=self.compound_lst, pdf_dir=self.out_path,
                         saved_pdf_name=self.saved_pdf_name)
        green_compound_lst = [
            i for i in self.compound_lst if i in self.color_compound_lst]

        self.reset_low_conc_compound_range(green_compound_lst)
        self.reset_low_conc_compound_conc(green_compound_lst)

        self.multi_core_draw_2(green_compound_lst, self.out_path)
        self.combine_pdf(compound_lst=green_compound_lst, pdf_dir=self.out_path,
                         saved_pdf_name=self.modified_pdf)

    def combine_pdf(self, compound_lst=None, pdf_dir=None,
                    saved_pdf_name=None):
        pdf_lst = []
        for compound in compound_lst:
            pdf_path = os.path.join(pdf_dir, compound + '.pdf')
            if os.path.exists(pdf_path):
                pdf_lst.append(pdf_path)

        opened_file = [open(file_name, 'rb') for file_name in pdf_lst]
        pdfFM = PyPDF2.PdfFileMerger()
        for file in opened_file:
            pdfFM.append(file, import_bookmarks=False)
        with open(saved_pdf_name, 'wb') as write_out_file:
            pdfFM.write(write_out_file)

        for file in opened_file:
            file.close()
        bookmark_dict = produce_page_dict(com_color_dict=self.compound_color_dict, pdf_dir=pdf_dir,
                                          compound_lst=compound_lst, compound_index_dict=self.compound_index_dict)
        insert_bookmark(saved_pdf_name, bookmark_dict)

        if self.rm_dir:
            shutil.rmtree(self.out_path)


if __name__ == "__main__":
    print(
        "Start : " +
        time.strftime(
            '%Y-%m-%d %H:%M:%S',
            time.localtime(
                time.time())))
    print("——" * 50)
    b = DrawPic(compound_list_excel='Q300定量物质信息配置表20240418.xlsx',
                peak_list='peaks.xlsx',
                conc_list='concentration.xlsx',
                intensity_list='intensity.xlsx',
                raw_dir='raw',
                saved_pdf_name='all_peak1.pdf',
                cpu_core_num=4,
                rm_dir=False,
                small_color='#00F200',
                big_color='#00F200',
                color_file='颜色配置文件.xlsx',
                color_group="A",
                k_cutoff=0.7,
                modified_pdf='green1.pdf',
                modified_conc_file='modified_conc.xlsx')

    b.multi_job()

    print(
        "Finish: " +
        time.strftime(
            '%Y-%m-%d %H:%M:%S',
            time.localtime(
                time.time())))
